﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.VisualStudio.Shell.Interop;
using EnvDTE;
using System.Xml;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Modeling;
using System.Collections;
using Microsoft.VisualStudio.Shell.Design;
using VSLangProj;
using devtm.Editor.TypeSelector.Dsl2Dte;
using System.Reflection;
using devtm.Editor.TypeSelector;
using System.IO;

namespace devtm.AutoMapper
{
    public static class TypeHelper
    {



        static TypeHelper()
        {
            _errors = new List<TypeDiscoveryError>();
        }

        private static List<TypeDiscoveryError> _errors { get; set; }
        public static IEnumerable<TypeDiscoveryError> Errors { get { return _errors; } }
        private static DynamicTypeService _typeService;
        public static IEnumerable<Type> GetAvailableTypes<T>(Store store)
        {

            _errors.Clear();

            if (store == null)
                yield break;

            DynamicTypeService typeService = _typeService ?? (_typeService = GetDynamicService(store));

            if (typeService == null)
                yield break;

            Project project = Store2DTE.GetProjectForStore(store);

            if (project == null)
                yield break;

            // les types de la solution
            IVsHierarchy Hierarchy = GetCurrentHierarchy(store);

            if (Hierarchy != null)
                foreach (var item in GetAvailableTypes<T>(Hierarchy, typeService, project))
                    yield return item;
            
        }

        private static DynamicTypeService GetDynamicService(Store store)
        {
            DynamicTypeService ret = (DynamicTypeService)store.GetService(typeof(Microsoft.VisualStudio.Shell.Design.DynamicTypeService));

            ret.AssemblyRefreshed += new AssemblyRefreshedEventHandler(ret_AssemblyRefreshed);

            return ret;
        }


        static void ret_AssemblyRefreshed(object sender, AssemblyRefreshedEventArgs e)
        {
            GetTypes(e.RefreshedAssembly, e.RefreshedTypes);
        }


        /// <summary>
        /// Gets the available types.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="excludeGlobalType">if set to <c>true</c> [exclude global type].</param>
        /// <param name="Hierarchy">The hierarchy is a Project item.</param>
        /// <returns></returns>
        public static IEnumerable<Type> GetAvailableTypes<T>(IVsHierarchy Hierarchy, DynamicTypeService typeService, Project project)
        {

            var prj = ToDteProject(Hierarchy);

            //var discovery = typeService.GetTypeDiscoveryService(Hierarchy); // La recherche s'effectue dans le scope du IVsHierarchy            
            var discovery = typeService.GetTypeResolutionService(Hierarchy); // La recherche s'effectue dans le scope du IVsHierarchy        

            if (discovery == null)
                yield break;

            // on récupere les projets référencés.
            devtm.Editor.TypeSelector.ParsingSolution.Project ab = new devtm.Editor.TypeSelector.ParsingSolution.Project(project);
            List<Project> ab2 = ab.GetProjectReferenceListe(false);


            // recupère les assemblies
            List<Assembly> lstAssemblies = GetAssemblies(discovery, ab2);



            // mettre en cash pour optimiser
            // la clef c est le crc de la dll.
            // si la dll existe avec un crc different on rafraichi

            // on récup les types
            foreach (Assembly assembly in lstAssemblies)
            {

                ICollection l = null;

                l = GetTypes(assembly);
         
                if (l != null)
                    foreach (Type type in l)
                        if (type != null)
                            yield return type;

            }

        }

        private static Dictionary<string, AssemblyContainer> _types = new Dictionary<string, AssemblyContainer>();
        private static ICollection GetTypes(Assembly assembly, Type[] types = null)
        {
            AssemblyContainer l = null;

            string n = assembly.FullName;

            if (_types.ContainsKey(n))
            {
                l = _types[n];
                if (types != null && types.Length > 0)
                {
                    l.Types = types;
                    return l.Types;
                }
                else
                {
                    if (l.LastWrite >= File.GetLastWriteTime(assembly.Location))
                    {
                        return l.Types;
                    }
                }

            }

            try
            {

                if (l == null)
                {
                    l = new AssemblyContainer();
                    _types.Add(n, l);
                    l.LastWrite = File.GetLastWriteTime(assembly.Location);
                }

                l.Types = assembly.GetTypes();
            }
            catch (ReflectionTypeLoadException ex)
            {
                foreach (Exception item in ex.LoaderExceptions)
                {
                    string msg = string.Format("loading {0} throwed exception. {1}. Try redo the reference. ", item.Message, assembly.GetName().Name);
                    _errors.Add(new TypeDiscoveryError() { Message = msg });
                }
                l.Types = ex.Types;
                l.LastWrite = l.LastWrite.AddDays(-1);
            }
           
            return l.Types;

        }

        //private static uint GetCrc(Assembly assembly)
        //{

        //    string f = assembly.Location;

        //    uint ret = Crc32.CalculateFile(f);
            
        //    return ret;

        //}


        private static List<Assembly> GetAssemblies(System.ComponentModel.Design.ITypeResolutionService discovery, List<Project> ab2)
        {

            List<Assembly> lstAssemblies = new List<Assembly>();

            foreach (Project item in ab2)
            {

                devtm.Editor.TypeSelector.ParsingSolution.Project ab = new devtm.Editor.TypeSelector.ParsingSolution.Project(item);

                try
                {
                    Assembly aas = discovery.GetAssembly(new AssemblyName(ab.AssemblyName), true);
                    lstAssemblies.Add(aas);
                }
                catch (Exception ex)
                {
                    TypeDiscoveryError err = new TypeDiscoveryError() { Message = ex.Message };
                    _errors.Add(err);
                }

            }

            return lstAssemblies;

        }
        

        public static IVsHierarchy GetCurrentHierarchy(IServiceProvider provider)
        {
            DTE vs = (DTE)provider.GetService(typeof(DTE));
            if (vs == null) throw new InvalidOperationException("DTE not found.");

            return ToHierarchy(vs.SelectedItems.Item(1).ProjectItem.ContainingProject);
        }

        public static IVsHierarchy GetCurrentHierarchy(Store store)
        {

            DTE vs = (DTE)store.GetService(typeof(DTE));
            if (vs == null) 
                throw new InvalidOperationException("DTE not found.");


            Project i2 = Store2DTE.GetProjectForStore(store);
            return ToHierarchy(i2);


            //var i = vs.SelectedItems.Item(1).ProjectItem;
            //if (i == null)
            //    return null;
            //return ToHierarchy(i.ContainingProject);

        }

        public static IVsHierarchy ToHierarchy(EnvDTE.Project project)
        {

            if (project == null)
                throw new ArgumentNullException("project");

            string projectGuid = null;

            // DTE does not expose the project GUID that exists at in the msbuild project file.
            // Cannot use MSBuild object model because it uses a static instance of the Engine, 
            // and using the Project will cause it to be unloaded from the engine when the 
            // GC collects the variable that we declare.
            using (XmlReader projectReader = XmlReader.Create(project.FileName))
            {
                projectReader.MoveToContent();
                object nodeName = projectReader.NameTable.Add("ProjectGuid");
                while (projectReader.Read())
                {
                    if (Object.Equals(projectReader.LocalName, nodeName))
                    {

                        projectGuid = projectReader.ReadElementContentAs(typeof(string), null).ToString();
                        break;
                    }
                }
            }

            Debug.Assert(!String.IsNullOrEmpty(projectGuid));

            IServiceProvider serviceProvider = new ServiceProvider(project.DTE as
                Microsoft.VisualStudio.OLE.Interop.IServiceProvider);

            return VsShellUtilities.GetHierarchy(serviceProvider, new Guid(projectGuid));
        }

        public static IVsProject3 ToVsProject(EnvDTE.Project project)
        {
            if (project == null) 
                throw new ArgumentNullException("project");

            IVsProject3 vsProject = ToHierarchy(project) as IVsProject3;
            
            if (vsProject == null)            
                throw new ArgumentException("Project is not a VS project.");
            
            return vsProject;
        }

        public static EnvDTE.Project ToDteProject(IVsHierarchy hierarchy)
        {
            if (hierarchy == null) 
                throw new ArgumentNullException("hierarchy");

            object prjObject = null;
            if (hierarchy.GetProperty(0xfffffffe, -2027, out prjObject) >= 0)            
                return (EnvDTE.Project)prjObject;            
            else            
                throw new ArgumentException("Hierarchy is not a project.");
            
        }

        public static EnvDTE.Project ToDteProject(IVsProject project)
        {
            if (project == null) 
                throw new ArgumentNullException("project");
            return ToDteProject(project as IVsHierarchy);
        }

    }
}
